/******************************************/
/************* GPStrac Server *************/
/******************************************/
/*
* FILE: gpsServer.c
* AUTHOR: Ram Subramanian and Nicolas Papin
* PROJECT: CS 3251 Project 2 - Professor Traynor
* DESCRIPTION: Application server code.
* PURPOSE: Defines the behavior of the GPStrac server for handling requests by clients.
*/

#include <string.h>
#include "gpsTrac.h"
#include "gpsServer.h"
#include "gpsConn.h"

int main(int argc, char *argv[])
{
    int socket = serverInit(SERV_PORT, MAX_PENDING);

    //Create some needed items.
    users = create_list();
    pthread_mutex_init(&userListMut, NULL);
    pthread_mutex_init(&fileMutex, NULL);
    pthread_cond_init(&userListCond, NULL);

    /* At some point it would be best to implement a way to exit without just terminating the server. */
    /* Another thought to add would be a userlist file option.  This would allow for detecting invalid users. */
    while(!exitFlg)
    {
        //Make sure we will not go over our max accepting connections, at least not by that much.
        //We could run into an issue where somehow, more connections are accepted before the previous one is added to the list, I will need to check this.
        pthread_mutex_lock(&userListMut);
        while(users->size >= MAX_CONNECTED)
            pthread_cond_wait(&userListCond, &userListMut);
        pthread_mutex_unlock(&userListMut);

        //Handle/Accept a new connection.
        handleConnection(socket, serveClient);
    }

    //Cleanup
    free(users);
    pthread_mutex_destroy(&userListMut);
    pthread_mutex_destroy(&fileMutex);
    pthread_cond_destroy(&userListCond);
}

void *serveClient(void *socket)
{
    /*
    * Set up variables that we need and interpret the socket number correctly.
    */
    pthread_mutex_lock(&userListMut);
    userNode_t ulEntry;
    memset(ulEntry.uname, 0, sizeof(ulEntry.uname));
    addNode(users, &ulEntry);
    pthread_mutex_unlock(&userListMut);

    ulEntry.socketNum = *(int *)socket;
    free(socket);
    ulEntry.lastPos.inval = 1;
    ulEntry.thread = pthread_self();
    struct sockaddr_in clientAddr;
    socklen_t clientSockLen;


    /* printf("Socket Accepted: %d\n", ulEntry.socketNum); */

    /*
    * If this errors, we may have a problem with our connection.
    * In this case, we close the connection and exit this handler.
    * It will be up to the client to reconnect.
    */
    if(getpeername(ulEntry.socketNum, (struct sockaddr *)&clientAddr, &clientSockLen) < 0)
        terminateConnection(&ulEntry, "Problem Resolving Socket - Terminating Connection.\n");

    printf("Awaiting identification of client at address %s.\n", inet_ntoa(*(struct in_addr*)&clientAddr.sin_addr.s_addr));
    /*
    * Check here to make sure that we get the username of the client.
    */
    getUserID(&ulEntry);

    printf("Connection on address %s identified as user %s.\nBeginning to handle requests for this user...\n", inet_ntoa(*(struct in_addr*)&clientAddr.sin_addr.s_addr), ulEntry.uname);

    /*
    * Now that we are actually logged in, we should handle all requests that we will normally be handling with this server.
    */
    processRequests(&ulEntry);


    printf("Connection to address %s for user %s terminated as expected.\n", inet_ntoa(*(struct in_addr *)&clientAddr.sin_addr.s_addr), ulEntry.uname);
    terminateConnection(&ulEntry, "Connection handling complete.");
}



void getUserID(userNode_t *userNode)
{
    int login = 0;
    clntReq_t request;
    servResp_t response;
    response.retLen = 0;

    char tmp[USER_ID_LEN];
    memset(tmp, 0, sizeof(tmp));

    while(!login)
    {
        defaultRecieve(userNode, &request, sizeof(clntReq_t));
        ntohRequest(&request);

        response.resp = ((request.cmd == LOG_IN)?(REQ_OK):(USER_INVALID));
        htonResponse(&response);
        defaultSend(userNode, &response, sizeof(servResp_t));
        ntohResponse(&response);

        if(response.resp == REQ_OK)
        {
            defaultRecieve(userNode, tmp, USER_ID_LEN);

            // TEST IF USER ALREADY LOGGED IN.
            pthread_mutex_lock(&userListMut);
            if(findWithName(users, tmp) != NULL)
            {
                printf("Refusing request to login as user that is already online.\n");
                response.resp = USER_INVALID;
            }
            else
            {
                printf("Requested username is valid and will be logged on.\n");
                response.resp = REQ_OK;
                strcpy(userNode->uname, tmp);
                login = 1;
            }
            pthread_mutex_unlock(&userListMut);

            htonResponse(&response);
            defaultSend(userNode, &response, sizeof(servResp_t));
            ntohResponse(&response);
        }
    }

    /*
    * Create our user's history file if it does not exist.
    */
    char fName[USER_ID_LEN + SAVE_STR_LEN];
    sprintf(fName, SAVE_STR, userNode->uname);

    FILE *fp;

    pthread_mutex_lock(&fileMutex);

    // Check if the file exists.  If not, we create it.
    if(!(fp = fopen(fName, "r")))
        fp = fopen(fName, "w");

    fclose(fp);
    pthread_mutex_unlock(&fileMutex);
}

void processRequests(userNode_t *userNode)
{
    int done = 0;
    clntReq_t request;
    servResp_t response;

    response.retLen = 0;

    while(!done)
    {
        response.resp = REQ_OK;

        /*
        * Get our request.
        */
        defaultRecieve(userNode, &request, sizeof(clntReq_t));
        ntohRequest(&request);

        switch(request.cmd)
        {
            case LOG_IN:
                response.resp = REQ_INVALID;
                htonResponse(&response);
                defaultSend(userNode, &response, sizeof(servResp_t));
                printf("REFUSED: Login request on authenticated connection by user %s not valid at this time.\n", userNode->uname);
                break;
            case UPDATE_LOC:
                htonResponse(&response);
                defaultSend(userNode, &response, sizeof(servResp_t));
                updateRequest(userNode, &request);
                break;
            case VIEW_LOC:
                htonResponse(&response);
                defaultSend(userNode, &response, sizeof(servResp_t));
                viewRequest(userNode, &request);
                break;
            case LOC_HIST:
                htonResponse(&response);
                defaultSend(userNode, &response, sizeof(servResp_t));
                histRequest(userNode, &request);
                break;
            case LEAVE:
                printf("Processing client %s disconnect request.\n", userNode->uname);
                htonResponse(&response);
                defaultSend(userNode, &response, sizeof(servResp_t));
                done = 1;
                break;
        }
    }
}

void updateRequest(userNode_t *userNode, clntReq_t *request)
{
    printf("\nHandling location update request for user: %s\n", userNode->uname);

    servResp_t response;
    response.retLen = 0;
    response.resp = REQ_OK;

    gpsCoord_t newPos;
    FILE *outfile;

    char fName[USER_ID_LEN + SAVE_STR_LEN];
    sprintf(fName, SAVE_STR, userNode->uname);

    // GET NEW LOCATION
    defaultRecieve(userNode, &newPos, sizeof(gpsCoord_t));
    ntohGPS(&newPos);

    newPos.inval = 0;

    pthread_mutex_lock(&fileMutex);
    outfile = fopen(fName, "a");
    if(!outfile)
    {
        pthread_mutex_unlock(&fileMutex);
        //terminateConnection(userNode, "Could not write to user file. Terminating connection.");

        response.resp = SERV_ERR;
        defaultSend(userNode, &response, sizeof(servResp_t));
        printf("Failed to write new location for %s.\n", userNode->uname);
        return;
    }

    fwrite(&newPos, sizeof(gpsCoord_t), 1, outfile);
    fclose(outfile);
    pthread_mutex_unlock(&fileMutex);

    pthread_mutex_lock(&userListMut);
    userNode->lastPos = newPos;
    pthread_mutex_unlock(&userListMut);

    defaultSend(userNode, &response, sizeof(servResp_t));

    printf("Successfully updated user %s position to new location at %d, %d.\n", userNode->uname, newPos.lat, newPos.lon);
}

void viewRequest(userNode_t *userNode, clntReq_t *request)
{
    printf("\nHandling friend location request for user: %s\n", userNode->uname);

    size_t numNames = request->msgLen / USER_ID_LEN;
    char reqNames[numNames][USER_ID_LEN];
    gpsCoord_t retCoord[numNames];

    servResp_t response;
    response.retLen = numNames * sizeof(gpsCoord_t);
    response.resp = REQ_OK;

    defaultRecieve(userNode, reqNames, request->msgLen);

    userNode_t *currUser;
    int i;

    for(i = 0; i < numNames; ++i)
    {
        pthread_mutex_lock(&userListMut);
        currUser = findWithName(users, reqNames[i]);
        pthread_mutex_unlock(&userListMut);

        if(currUser == NULL)
        {
            retCoord[i].inval = 1;
            retCoord[i].lat = 0;
            retCoord[i].lon = 0;

        }
        else
        {
            retCoord[i] = currUser->lastPos;
        }

        htonGPS(&retCoord[i]);
    }

    htonResponse(&response);
    defaultSend(userNode, &response, sizeof(servResp_t));
    ntohResponse(&response);

    defaultSend(userNode, retCoord, response.retLen);

    printf("Successfully handled friend location request for user: %s\n", userNode->uname);
}

void histRequest(userNode_t *userNode, clntReq_t *request)
{
    printf("\nHandling user history request for user: %s\n", userNode->uname);

    // SET RESPONSE LENGTH WHEN YOU KNOW IT
    servResp_t response;
    response.resp = REQ_OK;

    /* Get the user we are getting history of. */
    char userName[USER_ID_LEN];
    defaultRecieve(userNode, userName, USER_ID_LEN);

    printf("User %s requesting to view the history of user %s.\n", userNode->uname, userName);

    /* Make the file name. */
    char fName[USER_ID_LEN + SAVE_STR_LEN];
    sprintf(fName, SAVE_STR, userName);

    /*
    * Allocate memory to load the file into, otherwise notify that we have failed and return.
    */
    size_t currNum = 32;
    gpsCoord_t *hist = malloc(currNum * sizeof(gpsCoord_t));
    if(hist == NULL)
    {
        printf("Out of memory while handling user history request for user: %s.\n", userNode->uname);
        response.resp = SERV_ERR;
        response.retLen = 0;

        defaultSend(userNode, &response, sizeof(servResp_t));

        return;
    }

    gpsCoord_t *tmp;
    int i = 0;
    FILE *inFile;

    /* Read in the file. */
    pthread_mutex_lock(&fileMutex);
    inFile = fopen(fName, "r");

    /*
    * If we cannot open the file, notify that the user is likely invalid.
    */
    if(!inFile)
    {
        response.resp = USER_INVALID;
        response.retLen = 0;
    }
    else
    {
        /*
        * If we cann open, try to read in our gpsCoordinates.
        */
        while(fread(&hist[i], sizeof(gpsCoord_t), 1, inFile))
        {
            htonGPS(&hist[i]);

            ++i;

            /* We need more memory. */
            if(i >= currNum)
            {
                currNum *= 2;
                tmp = realloc(hist, currNum * sizeof(gpsCoord_t));
                /* We did not get the more memory we needed :( */
                if(tmp == NULL)
                {
                    printf("Out of memory while handling user history request for user: %s\n", userNode->uname);
                    response.resp = SERV_ERR;
                    response.retLen = 0;
                    break;
                }
                else
                {
                    hist = tmp;
                }
            }
        }

        /* As long as we did not error, set the response length. */
        if(response.resp != SERV_ERR)
            response.retLen = (i * sizeof(gpsCoord_t));

        fclose(inFile);
    }
    pthread_mutex_unlock(&fileMutex);

    htonResponse(&response);

    /* Send the response. */
    if(sendAmount(userNode->socketNum, &response, sizeof(servResp_t)))
    {
        free(hist);
        terminateConnection(userNode, "Client terminated connection unexpectedly. ");
    }

    ntohResponse(&response);

    /* Send further response if there is more to send. */
    if(response.retLen > 0)
    {
        if(sendAmount(userNode->socketNum, hist, response.retLen))
        {
            free(hist);
            terminateConnection(userNode, "Client terminated connection unexpectedly. ");
        }
    }

    free(hist);

    printf("Finished handling view history request by user: %s\n", userNode->uname);
}

void defaultRecieve(userNode_t *node, void *outBuffer, size_t amount)
{
    if(receiveAmount(node->socketNum, outBuffer, amount))
        terminateConnection(node, "Client terminated connection unexpectedly. ");
}

void defaultSend(userNode_t *node, const void *data, size_t amount)
{
    if(sendAmount(node->socketNum, data, amount))
        terminateConnection(node, "Client terminated connection unexpectedly. ");
}

void terminateConnection(userNode_t *node, char *msg)
{
    pthread_mutex_lock(&userListMut);
    removeNode(users, node);
    pthread_mutex_unlock(&userListMut);

    close(node->socketNum);

    pthread_cond_signal(&userListCond);

    /*
    * Store a logoff/connection close event.
    */
    char fName[USER_ID_LEN + SAVE_STR_LEN];
    sprintf(fName, SAVE_STR, node->uname);
    gpsCoord_t logoff;
    logoff.inval = 1;
    logoff.lat = 0;
    logoff.lon = 0;

    pthread_mutex_lock(&fileMutex);
    if(!(node->lastPos.inval) && (node->uname) > 0)
    {
        FILE *outfile = fopen(fName, "a");
        if(outfile)
        {
            fwrite(&logoff, sizeof(gpsCoord_t), 1, outfile);
            fclose(outfile);
        }
    }
    pthread_mutex_unlock(&fileMutex);

    printf("%s\n", msg);
    pthread_exit(NULL);
}
